aboutsummaryrefslogtreecommitdiff
path: root/pages/anime/[...id].js
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-07-16 22:35:39 +0700
committerFactiven <[email protected]>2023-07-16 22:35:39 +0700
commit1eee181e219dfd993d396ac3169e7aad3dd285eb (patch)
tree23fe54e9c3f8810f3ac9ab6b29070b4f0d4b9d20 /pages/anime/[...id].js
parentremoved console.log (diff)
downloadmoopa-1eee181e219dfd993d396ac3169e7aad3dd285eb.tar.xz
moopa-1eee181e219dfd993d396ac3169e7aad3dd285eb.zip
Update v3.6.4
- Added Manga page with a working tracker for AniList user - Added schedule component to home page - Added disqus comment section so you can fight on each other (not recommended) - Added /id and /en route for english and indonesian subs (id route still work in progress)
Diffstat (limited to 'pages/anime/[...id].js')
-rw-r--r--pages/anime/[...id].js1148
1 files changed, 0 insertions, 1148 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js
deleted file mode 100644
index a5429ed..0000000
--- a/pages/anime/[...id].js
+++ /dev/null
@@ -1,1148 +0,0 @@
-import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
-import "react-loading-skeleton/dist/skeleton.css";
-
-import {
- ChevronDownIcon,
- ClockIcon,
- HeartIcon,
-} from "@heroicons/react/20/solid";
-import {
- TvIcon,
- ArrowTrendingUpIcon,
- RectangleStackIcon,
-} from "@heroicons/react/24/outline";
-
-import Head from "next/head";
-import Image from "next/image";
-import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
-import Layout from "../../components/layout";
-import Link from "next/link";
-import Content from "../../components/hero/content";
-import Modal from "../../components/modal";
-
-import { signIn, useSession } from "next-auth/react";
-import AniList from "../../components/media/aniList";
-import ListEditor from "../../components/listEditor";
-
-import { GET_MEDIA_USER } from "../../queries";
-import { GET_MEDIA_INFO } from "../../queries";
-
-// import { aniInfo } from "../../components/devComp/data";
-// console.log(GET_MEDIA_USER);
-
-export default function Info({ info, color, api }) {
- // Episodes dropdown
- const [firstEpisodeIndex, setFirstEpisodeIndex] = useState(0);
- const [lastEpisodeIndex, setLastEpisodeIndex] = useState();
- const [selectedRange, setSelectedRange] = useState("All");
- function onEpisodeIndexChange(e) {
- if (e.target.value === "All") {
- setFirstEpisodeIndex(0);
- setLastEpisodeIndex();
- setSelectedRange("All");
- return;
- }
- setFirstEpisodeIndex(e.target.value.split("-")[0] - 1);
- setLastEpisodeIndex(e.target.value.split("-")[1]);
- setSelectedRange(e.target.value);
- }
-
- const { data: session } = useSession();
- const [episode, setEpisode] = useState(null);
- const [loading, setLoading] = useState(false);
- const [progress, setProgress] = useState(0);
- const [statuses, setStatuses] = useState(null);
- const [domainUrl, setDomainUrl] = useState("");
- const [showAll, setShowAll] = useState(false);
- const [visible, setVisible] = useState(false);
- const [open, setOpen] = useState(false);
- const [time, setTime] = useState(0);
- const { id } = useRouter().query;
-
- const [epiView, setEpiView] = useState("3");
-
- const [artStorage, setArtStorage] = useState(null);
-
- const rec = info?.recommendations?.nodes?.map(
- (data) => data.mediaRecommendation
- );
-
- const [provider, setProvider] = useState();
- const [prvValue, setPrvValue] = useState("gogoanime");
-
- const [availableProviders, setAvailableProviders] = useState([]);
- // const [err, setErr] = useState('');
-
- function handleProvider(e) {
- setEpisode(
- Array.isArray(provider[e.target.value])
- ? provider[e.target.value]?.reverse()
- : provider[e.target.value]
- );
- setPrvValue(e.target.value);
- localStorage.setItem("provider", e.target.value);
- }
-
- //for episodes dropdown
- useEffect(() => {
- setFirstEpisodeIndex(0);
- setLastEpisodeIndex();
- setSelectedRange("All");
- }, [info, prvValue]);
-
- useEffect(() => {
- handleClose();
- async function fetchData() {
- setLoading(true);
- if (id) {
- try {
- const { protocol, host } = window.location;
- const prv = localStorage.getItem("provider");
- const url = `${protocol}//${host}`;
-
- const view = localStorage.getItem("epiView");
-
- if (prv) {
- setPrvValue(prv);
- } else {
- setPrvValue("gogoanime");
- }
-
- setDomainUrl(url);
-
- setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings")));
-
- setEpisode(null);
- setProgress(0);
- setStatuses(null);
-
- let reloadCount = 0;
-
- try {
- const fetchPromises = [
- fetch(`${api}/meta/anilist/info/${info.id}?provider=enime`),
- fetch(`${api}/meta/anilist/info/${info.id}?provider=zoro`),
- fetch(`${api}/meta/anilist/info/${info.id}?provider=gogoanime`),
- ];
-
- const results = await Promise.allSettled(fetchPromises);
- const successfulResponses = [];
- let errorCount = 0;
-
- results.forEach((result) => {
- if (result.status === "fulfilled") {
- successfulResponses.push(result.value);
- } else {
- errorCount++;
- }
- });
-
- if (errorCount === fetchPromises.length) {
- // All fetch requests failed, handle the error here
- setEpisode([]);
- } else {
- // Process the successfulResponses here
- const responsesData = await Promise.all(
- successfulResponses.map((response) => response.json())
- );
- const [enime, zoro, gogoanime] = responsesData;
-
- const prov = {
- enime: enime?.episodes || enime,
- zoro: zoro?.episodes || zoro,
- gogoanime: gogoanime?.episodes || gogoanime,
- };
-
- const aPrv = [
- { name: "enime", available: enime?.episodes ? true : false },
- { name: "zoro", available: zoro?.episodes ? true : false },
- {
- name: "gogoanime",
- available: gogoanime?.episodes ? true : false,
- },
- ];
-
- setAvailableProviders(aPrv);
-
- const infProv = {
- enime: enime,
- zoro: zoro,
- gogoanime: gogoanime,
- };
-
- if (prv) {
- setEpisode(
- Array.isArray(prov[prv]) ? prov[prv]?.reverse() : prov[prv]
- );
- } else {
- setEpisode(
- Array.isArray(prov["gogoanime"])
- ? prov["gogoanime"]?.reverse()
- : prov["gogoanime"]
- );
- }
-
- const data = infProv[prv] || infProv["gogoanime"];
- // const data = aniInfo;
- if (!data || data?.episodes?.length === 0) {
- setEpisode([]);
- } else {
- if (data.episodes?.some((i) => i.title === null)) {
- setEpiView("3");
- } else if (view) {
- setEpiView(view);
- } else {
- setEpiView("3");
- }
- }
-
- if (session?.user?.name) {
- const response = await fetch("https://graphql.anilist.co/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: GET_MEDIA_USER,
- variables: {
- username: session?.user?.name,
- },
- }),
- });
-
- const responseData = await response.json();
-
- const prog = responseData?.data?.MediaListCollection;
-
- if (prog && prog.lists.length > 0) {
- const gut = prog.lists
- .flatMap((item) => item.entries)
- .find((item) => item.mediaId === parseInt(id[0]));
-
- if (gut) {
- setProgress(gut.progress);
- const statusMapping = {
- CURRENT: { name: "Watching", value: "CURRENT" },
- PLANNING: { name: "Plan to watch", value: "PLANNING" },
- COMPLETED: { name: "Completed", value: "COMPLETED" },
- DROPPED: { name: "Dropped", value: "DROPPED" },
- PAUSED: { name: "Paused", value: "PAUSED" },
- REPEATING: { name: "Rewatching", value: "REPEATING" },
- };
- setStatuses(statusMapping[gut.status]);
- }
- }
- }
-
- if (data.nextAiringEpisode) {
- setTime(
- convertSecondsToTime(data.nextAiringEpisode.timeUntilAiring)
- );
- }
-
- setProvider(prov);
- }
- } catch (error) {
- console.error(error);
- if (reloadCount < 2) {
- reloadCount++;
- setTimeout(() => {
- window.location.reload();
- }, 1000);
- } else {
- setEpisode([]);
- }
- }
- } catch (error) {
- console.error(error);
- setTimeout(() => {
- window.location.reload();
- }, 1000);
- } finally {
- setLoading(false);
- }
- }
- }
- fetchData();
- }, [id, info, session?.user?.name]);
-
- function handleOpen() {
- setOpen(true);
- document.body.style.overflow = "hidden";
- }
-
- function handleClose() {
- setOpen(false);
- document.body.style.overflow = "auto";
- }
-
- return (
- <>
- <Head>
- <title>
- {info
- ? info?.title?.romaji || info?.title?.english
- : "Retrieving Data..."}
- </title>
- <meta name="twitter:card" content="summary_large_image" />
- <meta
- name="twitter:title"
- content={`Moopa - ${info.title.romaji || info.title.english}`}
- />
- <meta
- name="twitter:description"
- content={`${info.description?.slice(0, 180)}...`}
- />
- <meta
- name="twitter:image"
- content={`${domainUrl}/api/og?title=${
- info.title.romaji || info.title.english
- }&image=${info.bannerImage || info.coverImage.extraLarge}`}
- />
- </Head>
- <Modal open={open} onClose={() => handleClose()}>
- <div>
- {!session && (
- <div className="flex-center flex-col gap-5 px-10 py-5 bg-secondary rounded-md">
- <h1 className="text-md font-extrabold font-karla">
- Edit your list
- </h1>
- <button
- className="flex items-center bg-[#363642] rounded-md text-white p-1"
- onClick={() => signIn("AniListProvider")}
- >
- <h1 className="px-1 font-bold font-karla">
- Login with AniList
- </h1>
- <div className="scale-[60%] pb-[1px]">
- <AniList />
- </div>
- </button>
- </div>
- )}
- {session && info && (
- <ListEditor
- animeId={info?.id}
- session={session}
- stats={statuses}
- prg={progress}
- max={info?.episodes}
- image={info}
- />
- )}
- </div>
- </Modal>
- <SkeletonTheme baseColor="#232329" highlightColor="#2a2a32">
- <Layout navTop="text-white bg-primary lg:pt-0 lg:px-0 bg-slate bg-opacity-40 z-50">
- <div className="w-screen min-h-screen relative flex flex-col items-center bg-primary gap-5">
- <div className="bg-image w-screen">
- <div className="bg-gradient-to-t from-primary from-10% to-transparent absolute h-[300px] w-screen z-10 inset-0" />
- {info ? (
- <Image
- src={
- info?.bannerImage ||
- info?.coverImage?.extraLarge ||
- info?.coverImage.large
- }
- priority={true}
- alt="banner anime"
- height={1000}
- width={1000}
- className="object-cover bg-image w-screen absolute top-0 left-0 h-[300px] brightness-[70%] z-0"
- />
- ) : (
- <div className="bg-image w-screen absolute top-0 left-0 h-[300px]" />
- )}
- </div>
- <div className="lg:w-[90%] xl:w-[75%] lg:pt-[10rem] z-30 flex flex-col gap-5">
- {/* Mobile */}
-
- <div className="lg:hidden pt-5 w-screen px-5 flex flex-col">
- <div className="h-[250px] flex flex-col gap-1 justify-center">
- <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]">
- {info?.title?.romaji || info?.title?.english}
- </h1>
- <p
- className="line-clamp-2 text-sm font-light antialiased w-[56%]"
- dangerouslySetInnerHTML={{ __html: info?.description }}
- />
- <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]">
- {info?.genres
- ?.slice(
- 0,
- info?.genres?.length > 3 ? info?.genres?.length : 3
- )
- .map((item, index) => (
- <span
- key={index}
- className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full"
- >
- <span className="">{item}</span>
- </span>
- ))}
- </div>
- {info && (
- <div className="flex items-center gap-5 pt-3 text-center">
- <div className="flex items-center gap-2 text-center">
- <button
- type="button"
- className="bg-action px-10 rounded-sm font-karla font-bold"
- onClick={() => handleOpen()}
- >
- {!loading
- ? statuses
- ? statuses.name
- : "Add to List"
- : "Loading..."}
- </button>
- <div className="h-6 w-6">
- <HeartIcon />
- </div>
- </div>
- </div>
- )}
- </div>
- <div className="bg-secondary rounded-sm xs:h-[30px]">
- <div className="grid grid-cols-3 place-content-center xxs:flex items-center justify-center h-full xxs:gap-10 p-2 text-sm">
- {info && info.status !== "NOT_YET_RELEASED" ? (
- <>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <TvIcon className="w-5 h-5 text-action" />
- <h4 className="font-karla">{info?.type}</h4>
- </div>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <ArrowTrendingUpIcon className="w-5 h-5 text-action" />
- <h4>{info?.averageScore}%</h4>
- </div>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <RectangleStackIcon className="w-5 h-5 text-action" />
- {info?.episodes ? (
- <h1>{info?.episodes} Episodes</h1>
- ) : (
- <h1>TBA</h1>
- )}
- </div>
- </>
- ) : (
- <div>{info && "Not Yet Released"}</div>
- )}
- </div>
- </div>
- </div>
-
- {/* PC */}
- <div className="hidden lg:flex gap-8 w-full flex-nowrap">
- <div className="shrink-0 lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] relative">
- {info ? (
- <>
- <div className="bg-image lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] bg-opacity-30 absolute backdrop-blur-lg z-10 -top-7" />
- <Image
- src={
- info.coverImage.extraLarge || info.coverImage.large
- }
- priority={true}
- alt="poster anime"
- height={700}
- width={700}
- className="object-cover lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] z-20 absolute rounded-md -top-7"
- />
- <button
- type="button"
- className="bg-action flex-center z-20 h-[20px] w-[180px] absolute bottom-0 rounded-sm font-karla font-bold"
- onClick={() => handleOpen()}
- >
- {!loading
- ? statuses
- ? statuses.name
- : "Add to List"
- : "Loading..."}
- </button>
- </>
- ) : (
- <Skeleton className="h-[250px] w-[180px]" />
- )}
- </div>
-
- {/* PC */}
- <div className="hidden lg:flex w-full flex-col gap-5 h-[250px]">
- <div className="flex flex-col gap-2">
- <h1 className=" font-inter font-bold text-[36px] text-white line-clamp-1">
- {info ? (
- info?.title?.romaji || info?.title?.english
- ) : (
- <Skeleton width={450} />
- )}
- </h1>
- {info ? (
- <div className="flex gap-6">
- {info?.episodes && (
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- {info?.episodes} Episodes
- </div>
- )}
- {info?.startDate?.year && (
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- {info?.startDate?.year}
- </div>
- )}
- {info?.averageScore && (
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- {info?.averageScore}%
- </div>
- )}
- {info?.type && (
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- {info?.type}
- </div>
- )}
- {info?.status && (
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- {info?.status}
- </div>
- )}
- <div
- className={`dynamic-text rounded-md px-2 font-karla font-bold`}
- style={color}
- >
- Sub | EN
- </div>
- </div>
- ) : (
- <Skeleton width={240} height={32} />
- )}
- </div>
- {info ? (
- <p
- dangerouslySetInnerHTML={{ __html: info?.description }}
- className="overflow-y-scroll scrollbar-thin pr-2 scrollbar-thumb-secondary scrollbar-thumb-rounded-lg h-[140px]"
- />
- ) : (
- <Skeleton className="h-[130px]" />
- )}
- </div>
- </div>
-
- <div>
- <div className="flex gap-5 items-center">
- {info?.relations?.edges?.length > 0 && (
- <div className="p-3 lg:p-0 text-[20px] lg:text-2xl font-bold font-karla">
- Relations
- </div>
- )}
- {info?.relations?.edges?.length > 3 && (
- <div
- className="cursor-pointer"
- onClick={() => setShowAll(!showAll)}
- >
- {showAll ? "show less" : "show more"}
- </div>
- )}
- </div>
- <div
- className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 lg:pb-5 px-3 lg:px-4 pt-4 rounded-xl`}
- >
- {info?.relations?.edges ? (
- info?.relations?.edges
- .slice(0, showAll ? info?.relations?.edges.length : 3)
- .map((r, index) => {
- const rel = r.node;
- return (
- <Link
- key={rel.id}
- href={
- rel.type === "ANIME" ||
- rel.type === "OVA" ||
- rel.type === "MOVIE" ||
- rel.type === "SPECIAL" ||
- rel.type === "ONA"
- ? `/anime/${rel.id}`
- : `/manga/detail/id?aniId=${
- rel.id
- }&aniTitle=${encodeURIComponent(
- info?.title?.english ||
- info?.title.romaji ||
- info?.title.native
- )}`
- }
- className={`hover:scale-[1.02] hover:shadow-lg lg:px-0 px-4 scale-100 transition-transform duration-200 ease-out w-full ${
- rel.type === "MUSIC" ? "pointer-events-none" : ""
- }`}
- >
- <div
- key={rel.id}
- className="w-full shrink h-[126px] bg-secondary flex rounded-md"
- >
- <div className="w-[90px] bg-image rounded-l-md shrink-0">
- <Image
- src={
- rel.coverImage.extraLarge ||
- rel.coverImage.large
- }
- alt={rel.id}
- height={500}
- width={500}
- className="object-cover h-full w-full shrink-0 rounded-l-md"
- />
- </div>
- <div className="h-full grid px-3 items-center">
- <div className="text-action font-outfit font-bold">
- {r.relationType}
- </div>
- <div className="font-outfit font-thin line-clamp-2">
- {rel.title.userPreferred || rel.title.romaji}
- </div>
- <div className={``}>{rel.type}</div>
- </div>
- </div>
- </Link>
- );
- })
- ) : (
- <>
- {[1, 2, 3].map((item) => (
- <div key={item} className="w-full hidden lg:block">
- <Skeleton className="h-[126px]" />
- </div>
- ))}
- <div className="w-full lg:hidden">
- <Skeleton className="h-[126px]" />
- </div>
- </>
- )}
- </div>
- </div>
- <div className="flex flex-col gap-5 lg:gap-10 p-3 lg:p-0">
- <div className="flex lg:flex-row flex-col gap-5 lg:gap-0 justify-between ">
- <div className="flex justify-between">
- <div className="flex items-center lg:gap-10 sm:gap-7 gap-3">
- {info && (
- <h1 className="text-[20px] lg:text-2xl font-bold font-karla">
- Episodes
- </h1>
- )}
- {info?.nextAiringEpisode && (
- <div className="flex items-center gap-2">
- <div className="flex items-center gap-4 text-[10px] xxs:text-sm lg:text-base">
- <h1>Next :</h1>
- <div className="px-4 rounded-sm font-karla font-bold bg-white text-black">
- {time}
- </div>
- </div>
- <div className="h-6 w-6">
- <ClockIcon />
- </div>
- </div>
- )}
- </div>
- <div
- className="lg:hidden bg-secondary p-1 rounded-md cursor-pointer"
- onClick={() => setVisible(!visible)}
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="w-6 h-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
- />
- </svg>
- </div>
- </div>
- <div
- className={`flex lg:flex items-center gap-0 lg:gap-5 justify-between ${
- visible ? "" : "hidden"
- }`}
- >
- <div className="flex items-end gap-3">
- <div className="relative flex gap-2 items-center">
- <p className="hidden md:block">Provider</p>
- <select
- onChange={handleProvider}
- value={prvValue}
- className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus:ring-1 focus:ring-action"
- >
- {availableProviders
- ?.filter((p) => p.available === true)
- .map((p) => {
- return (
- <option key={p.name} value={p.name}>
- {p.name}
- </option>
- );
- })}
- </select>
- <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" />
- </div>
- {episode?.length > 50 && (
- <div className="relative flex gap-2 items-center">
- <p className="hidden md:block">Episodes</p>
- <select
- onChange={onEpisodeIndexChange}
- value={selectedRange}
- className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus:ring-1 focus:ring-action scrollbar-thin scrollbar-thumb-secondary scrollbar-thumb-rounded-lg"
- >
- <option value="All">All</option>
- {[...Array(Math.ceil(episode?.length / 50))].map(
- (_, index) => {
- const start = index * 50 + 1;
- const end = Math.min(
- start + 50 - 1,
- episode?.length
- );
- const optionLabel = `${start} to ${end}`;
- if (episode[0]?.number !== 1) {
- var valueLabel = `${
- episode.length - end + 1
- }-${episode.length - start + 1}`;
- } else {
- var valueLabel = `${start}-${end}`;
- }
- return (
- <option key={valueLabel} value={valueLabel}>
- {optionLabel}
- </option>
- );
- }
- )}
- </select>
- <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" />
- </div>
- )}
- </div>
- <div className="flex gap-3 rounded-sm items-center p-2">
- <div
- className={
- episode?.length > 0
- ? episode?.some((item) => item?.title === null)
- ? "pointer-events-none"
- : "cursor-pointer"
- : "pointer-events-none"
- }
- onClick={() => {
- setEpiView("1");
- localStorage.setItem("epiView", "1");
- }}
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="31"
- height="20"
- fill="none"
- viewBox="0 0 31 20"
- >
- <rect
- width="31"
- height="20"
- className={`${
- episode?.length > 0
- ? episode?.some((item) => item?.title === null)
- ? "fill-[#1c1c22]"
- : epiView === "1"
- ? "fill-action"
- : "fill-[#3A3A44]"
- : "fill-[#1c1c22]"
- }`}
- rx="3"
- ></rect>
- </svg>
- </div>
- <div
- className={
- episode?.length > 0
- ? episode?.some((item) => item?.title === null)
- ? "pointer-events-none"
- : "cursor-pointer"
- : "pointer-events-none"
- }
- onClick={() => {
- setEpiView("2");
- localStorage.setItem("epiView", "2");
- }}
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="33"
- height="20"
- fill="none"
- className={`${
- episode?.length > 0
- ? episode?.some((item) => item?.title === null)
- ? "fill-[#1c1c22]"
- : epiView === "2"
- ? "fill-action"
- : "fill-[#3A3A44]"
- : "fill-[#1c1c22]"
- }`}
- viewBox="0 0 33 20"
- >
- <rect width="33" height="7" y="1" rx="3"></rect>
- <rect width="33" height="7" y="12" rx="3"></rect>
- </svg>
- </div>
- <div
- className={
- episode?.length > 0
- ? `cursor-pointer`
- : "pointer-events-none"
- }
- onClick={() => {
- setEpiView("3");
- localStorage.setItem("epiView", "3");
- }}
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="33"
- height="20"
- fill="none"
- className={`${
- episode?.length > 0
- ? epiView === "3"
- ? "fill-action"
- : "fill-[#3A3A44]"
- : "fill-[#1c1c22]"
- }`}
- viewBox="0 0 33 20"
- >
- <rect width="29" height="4" x="2" y="2" rx="2"></rect>
- <rect width="29" height="4" x="2" y="8" rx="2"></rect>
- <rect
- width="16"
- height="4"
- x="2"
- y="14"
- rx="2"
- ></rect>
- </svg>
- </div>
- </div>
- </div>
- </div>
- {!loading ? (
- Array.isArray(episode) ? (
- episode && (
- <div
- className={`${
- epiView === "3" &&
- "scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] h-[640px]"
- }`}
- >
- {episode?.length !== 0 && episode ? (
- <div
- className={`grid ${
- epiView === "1"
- ? "grid-auto-fit gap-5 lg:gap-8"
- : "flex flex-col gap-5"
- } pb-5 pt-2 lg:pt-0 ${
- epiView === "3" ? "" : "place-items-center"
- }`}
- >
- {epiView === "1"
- ? episode
- .slice(firstEpisodeIndex, lastEpisodeIndex)
- ?.map((epi, index) => {
- const time = artStorage?.[epi?.id]?.time;
- const duration =
- artStorage?.[epi?.id]?.duration;
- let prog = (time / duration) * 100;
- if (prog > 90) prog = 100;
- return (
- <Link
- key={index}
- href={`/anime/watch/${epi.id}/${info.id}/${prvValue}`}
- className="transition-all duration-200 ease-out lg:hover:scale-105 hover:ring-1 hover:ring-white cursor-pointer bg-secondary shrink-0 relative w-full h-[180px] sm:h-[130px] subpixel-antialiased rounded-md overflow-hidden"
- >
- <span className="absolute text-sm z-40 bottom-1 left-2 font-karla font-semibold text-white">
- Episode {epi?.number}
- </span>
- <span
- className={`absolute bottom-7 left-0 h-1 bg-red-600`}
- style={{
- width:
- progress &&
- artStorage &&
- epi?.number <= progress
- ? "100%"
- : artStorage?.[epi?.id]
- ? `${prog}%`
- : "0%",
- }}
- />
- <div className="absolute inset-0 bg-black z-30 opacity-20" />
- <Image
- src={epi?.image}
- alt="epi image"
- width={500}
- height={500}
- className="object-cover w-full h-[150px] sm:h-[100px] z-20"
- />
- </Link>
- );
- })
- : ""}
- {epiView === "2" &&
- episode
- .slice(firstEpisodeIndex, lastEpisodeIndex)
- .map((epi, index) => {
- const time = artStorage?.[epi?.id]?.time;
- const duration =
- artStorage?.[epi?.id]?.duration;
- let prog = (time / duration) * 100;
- if (prog > 90) prog = 100;
- return (
- <Link
- key={index}
- href={`/anime/watch/${epi.id}/${info.id}/${prvValue}`}
- className="flex group h-[110px] lg:h-[160px] w-full rounded-lg transition-all duration-300 ease-out bg-secondary cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white"
- >
- <div className="w-[43%] lg:w-[30%] relative shrink-0 z-40 rounded-lg overflow-hidden shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)]">
- <div className="relative">
- <Image
- src={epi?.image}
- alt="Anime Cover"
- width={1000}
- height={1000}
- className="object-cover z-30 rounded-lg h-[110px] lg:h-[160px] brightness-[65%]"
- />
- <span
- className={`absolute bottom-0 left-0 h-[3px] bg-red-700`}
- style={{
- width:
- progress &&
- artStorage &&
- epi?.number <= progress
- ? "100%"
- : artStorage?.[epi?.id]
- ? `${prog}%`
- : "0",
- }}
- />
- <span className="absolute bottom-2 left-2 font-karla font-semibold text-sm lg:text-lg">
- Episode {epi?.number}
- </span>
- <div className="z-[9999] absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-[1.5]">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- className="w-5 h-5 invisible group-hover:visible"
- >
- <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" />
- </svg>
- </div>
- </div>
- </div>
-
- <div
- className={`w-[70%] h-full select-none p-4 flex flex-col justify-center gap-5 ${
- epi?.id == id ? "text-[#7a7a7a]" : ""
- }`}
- >
- <h1 className="font-karla font-bold text-base lg:text-lg xl:text-xl italic line-clamp-1">
- {epi?.title}
- </h1>
- {epi?.description && (
- <p className="line-clamp-2 text-xs lg:text-md xl:text-lg italic font-outfit font-extralight">
- {epi?.description}
- </p>
- )}
- </div>
- </Link>
- );
- })}
- {epiView === "3" &&
- episode
- .slice(firstEpisodeIndex, lastEpisodeIndex)
- .map((epi, index) => {
- return (
- <div
- key={index}
- className="flex flex-col gap-3 px-2"
- >
- <Link
- href={`/anime/watch/${epi.id}/${info.id}/${prvValue}`}
- className={`text-start text-sm lg:text-lg ${
- progress && epi.number <= progress
- ? "text-[#5f5f5f]"
- : "text-white"
- }`}
- >
- <p>Episode {epi.number}</p>
- {epi.title && (
- <p
- className={`text-xs lg:text-sm ${
- progress && epi.number <= progress
- ? "text-[#5f5f5f]"
- : "text-[#b1b1b1]"
- } italic`}
- >
- "{epi.title}"
- </p>
- )}
- </Link>
- {index !== episode?.length - 1 && (
- <span className="h-[1px] bg-white" />
- )}
- </div>
- );
- })}
- </div>
- ) : (
- <p>No Episodes Available</p>
- )}
- </div>
- )
- ) : (
- <div className="flex flex-col">
- <pre
- className={`rounded-md overflow-hidden ${getLanguageClassName(
- "bash"
- )}`}
- >
- <code>{episode?.message}</code>
- </pre>
- </div>
- )
- ) : (
- <div className="flex justify-center">
- <div className="lds-ellipsis">
- <div></div>
- <div></div>
- <div></div>
- <div></div>
- </div>
- </div>
- )}
- </div>
- </div>
- {info && rec?.length !== 0 && (
- <div className="w-screen lg:w-[90%] xl:w-[85%]">
- <Content
- ids="recommendAnime"
- section="Recommendations"
- data={rec}
- />
- </div>
- )}
- </div>
- </Layout>
- </SkeletonTheme>
- </>
- );
-}
-
-export async function getServerSideProps(context) {
- const { id } = context.query;
- const API_URI = process.env.API_URI;
-
- const res = await fetch("https://graphql.anilist.co/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: GET_MEDIA_INFO,
- variables: {
- id: id?.[0],
- },
- }),
- });
-
- const json = await res.json();
- const data = json?.data?.Media;
-
- if (!data) {
- return {
- notFound: true,
- };
- }
-
- const textColor = setTxtColor(data?.coverImage?.color);
-
- const color = {
- backgroundColor: `${data?.coverImage?.color || "#ffff"}`,
- color: textColor,
- };
-
- return {
- props: {
- info: data,
- color: color,
- api: API_URI,
- },
- };
-}
-
-function convertSecondsToTime(sec) {
- let days = Math.floor(sec / (3600 * 24));
- let hours = Math.floor((sec % (3600 * 24)) / 3600);
- let minutes = Math.floor((sec % 3600) / 60);
-
- let time = "";
-
- if (days > 0) {
- time += `${days}d `;
- }
-
- if (hours > 0) {
- time += `${hours}h `;
- }
-
- if (minutes > 0) {
- time += `${minutes}m `;
- }
-
- return time.trim();
-}
-
-function getBrightness(hexColor) {
- if (!hexColor) {
- return 200;
- }
- const rgb = hexColor
- .match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i)
- .slice(1)
- .map((x) => parseInt(x, 16));
- return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000;
-}
-
-function setTxtColor(hexColor) {
- const brightness = getBrightness(hexColor);
- return brightness < 150 ? "#fff" : "#000";
-}
-
-const getLanguageClassName = (language) => {
- switch (language) {
- case "javascript":
- return "language-javascript";
- case "html":
- return "language-html";
- case "bash":
- return "language-bash";
- // add more languages here as needed
- default:
- return "";
- }
-};